/*
Copyright (C) 2011 Ing. Marek Hubal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <stdio.h>
#include <string.h>
#include "cbi.h"

//#define _CRTDBG_MAP_ALLOC
//#include <stdlib.h>
//#include <crtdbg.h>

#pragma warning (disable: 4996)

#define FUNCTION_ENCODE 0
#define FUNCTION_DECODE 1
#define FUNCTION_STREAM_INFO 2

#define VERSION "0.7.2"

static int InParam_Function;
static int InParam_CompressionMode;
static char InParam_InFileName[256*1024];
static char InParam_OutFileName[300];
static int InParam_Pages[10240];
static int InParam_PagesCount;

// ....................................................................................................................
// Zobrazenie pouzitia
// ....................................................................................................................
void __fastcall PrintUsage(void) {
	printf("\n..........................................................................");
	printf("\ncbi console version %s", VERSION);
	printf("\n..........................................................................");
	printf("\nUsage:");
	printf("\n..........................................................................");
	printf("\ncbi_console -f=(e|d|i) -cm=(0..2) -in=(in_file_name) [-page=n1, n2..nx] [-out=(out_file_name)]");
	printf("\n\t-f - function");
	printf("\n\t\te - encode, d - decode, i - stream info");
	printf("\n\t-cm - compression mode");
	printf("\n\t\tpossible values: 0, 1");
	printf("\n\t-in - input file name for decode/info");
	printf("\n\t-in - 1..n file names for encode separated by semicolon");
	printf("\n\t-out - output file name for encode/decode");
	printf("\n\t-page - zero based indexes of decoded pages, if none all will be decoded");
	printf("\n..........................................................................");
	printf("\n");
}
// ....................................................................................................................

// ....................................................................................................................
// Dekodovanie vstupnych parametrov
// ....................................................................................................................
bool __fastcall DecodeInputParams(int pArgc, char** pArgs) {
	InParam_Function = FUNCTION_ENCODE; // Inicializujem
	InParam_CompressionMode = 0;
	*InParam_InFileName = 0;
	*InParam_OutFileName = 0;
	InParam_PagesCount = 0;

	for (int i = 1; i < pArgc; i++) { // Prejdem vsetky parametre
		if (strncmp(pArgs[i], "-f=", 3) == 0) { // Je to funkcia?
			switch(pArgs[i][3]) {
				case 'e':
				case 'E':
					InParam_Function = FUNCTION_ENCODE; break;
				case 'd':
				case 'D':
					InParam_Function = FUNCTION_DECODE; break;
				case 'i':
				case 'I':
					InParam_Function = FUNCTION_STREAM_INFO; break;
			}
			continue;
		}
		if (strncmp(pArgs[i], "-cm=", 4) == 0) { // Je to kompresna metoda?
			int R; 
			if (sscanf(pArgs[i] + 4, "%ld", &R) == 1) InParam_CompressionMode = R;
			continue;
		}
		if (strncmp(pArgs[i], "-in=", 4) == 0) { // Je to nazov vstupu?
			strcpy(InParam_InFileName, pArgs[i] + 4);
			continue;
		}
		if (strncmp(pArgs[i], "-out=", 5) == 0) { // Je to nazov vstupu?
			strncpy(InParam_OutFileName, pArgs[i] + 5, 255);
			continue;
		}
		if (strncmp(pArgs[i], "-pages=", 7) == 0) { // Su to cisla stranok?

			char BUF[256];
			char* P = pArgs[i] + 7;
			while(true) {
				char *P1 = strchr(P, ',');
				if (P1 == NULL) strncpy(BUF, P, 255);
				else strncpy(BUF, P, P1 - P);

				int R;
				if (sscanf(BUF, "%lu", &R) == 1) InParam_Pages[InParam_PagesCount++] = R;
				if (P1 == NULL) break;
				P = P1 + 1;
			}

			continue;
		}
	}

	// Test pripustnosti a korekcie zadanych parametrov
	if (!((InParam_Function >= FUNCTION_ENCODE) && (InParam_Function <= FUNCTION_STREAM_INFO))) return false;
	if (!((InParam_CompressionMode >= 0) && (InParam_CompressionMode <= 3))) return false;
	if (*InParam_InFileName == 0) return false;
	if (*InParam_OutFileName == 0) {
		char BUF[300];
		char *P = strchr(InParam_InFileName, ';');
		if (P != NULL) strncpy(BUF, InParam_InFileName, P - InParam_InFileName);
		else strncpy(BUF, InParam_InFileName, 256);
		P = strrchr(BUF, '.');
		if (P == NULL) strcpy(InParam_OutFileName, BUF);
		else strncpy(InParam_OutFileName, BUF, P - BUF);
		if (InParam_Function == FUNCTION_ENCODE) strcat(InParam_OutFileName, ".cbi");
		else strcat(InParam_OutFileName, ".bmp");
	}

	return true;
}
// ....................................................................................................................

// ....................................................................................................................
// Spustenie funkcie 'encode'
// ....................................................................................................................
void __fastcall RunEncode(void) {
	printf("\n..........................................................................");
	printf("\ncbi console version %s", VERSION);
	printf("\n..........................................................................");
	printf("\nEncode:");
	printf("\n..........................................................................");
	printf("\nCompression method: %ld", InParam_CompressionMode);
	printf("\nOutput file: %s", InParam_OutFileName);

	char* FileNamesCopy = new char[strlen(InParam_InFileName) + 1]; // Skopirujem do lokalnej kopie
	strcpy(FileNamesCopy, InParam_InFileName); 

	int InFilesCount = 1;
	char* P = FileNamesCopy;
	while(true) { // Ziskam pocet vstupnych suborov
		char* P1 = strchr(P, ';');
		if (P1 == NULL) break;
		P = P1 + 1;
		InFilesCount++;
	}

	char **FileNames = new char*[InFilesCount];
	P = FileNamesCopy;
	for (int i = 0; i < InFilesCount; i++) {
		FileNames[i] = P;
		char* P1 = strchr(P, ';');
		if (P1 != NULL) *P1 = 0; // Ukoncim aktualny retazec
		P = P1 + 1;
	}


	printf("\nInput files count: %ld", InFilesCount);
	for (int i = 0; i < InFilesCount; i++) {
		printf("\n\tInput files %ld: '%s'", i + 1, FileNames[i]);
	}
	printf("\n..........................................................................");

	CONVERT_PARAMS Params; Params.STRUCTURE_SIZE = sizeof(CONVERT_PARAMS);
	Params.COMPRESSION_METHOD = InParam_CompressionMode;

	double ET = 0;

	printf("\nEncoding... ");

	void *Stream = cbi_create_stream(InParam_OutFileName, &Params);
	if (Stream == NULL) {
		switch(Params.ERROR_CODE) {
			case BMP_ERROR_OPEN_FILE: printf("ERROR. Cannot open input BMP file."); break; 
			case BMP_ERROR_READ_FILE: printf("ERROR. Cannot read from input BMP file."); break; 
			case BMP_ERROR_FILE_HEADER_CORRUPTED: printf("ERROR. BMP file header is corrupted."); break; 
			case BMP_ERROR_UNSUPORTED_BITS_PER_PIXEL: printf("ERROR. Unsupported BMP file."); break; 
			case BMP_ERROR_UNSUPORTED_COMPRESSION: printf("ERROR. Unsupported BMP file."); break; 
			case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
			case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
			case CBI_ERROR_CREATE_FILE: printf("ERROR. Cannot create output CBI stream."); break; 
			case CBI_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output CBI stream."); break; 
		}
		delete FileNames;
		delete FileNamesCopy;
		return;
	}

	ET += Params.ELAPSED_TIME;

	int R;
	for (int i = 0; i < InFilesCount; i++) {
		if (*FileNames[i] == 0) continue;
		R = cbi_add_bmp_page_to_stream(Stream, FileNames[i], &Params);
		if (R != 0) {
			cbi_close_stream(Stream, &Params);
			switch(R) {
				case BMP_ERROR_OPEN_FILE: printf("ERROR. Cannot open input BMP file."); break; 
				case BMP_ERROR_READ_FILE: printf("ERROR. Cannot read from input BMP file."); break; 
				case BMP_ERROR_FILE_HEADER_CORRUPTED: printf("ERROR. BMP file header is corrupted."); break; 
				case BMP_ERROR_UNSUPORTED_BITS_PER_PIXEL: printf("ERROR. Unsupported BMP file."); break; 
				case BMP_ERROR_UNSUPORTED_COMPRESSION: printf("ERROR. Unsupported BMP file."); break; 
				case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
				case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
				case CBI_ERROR_CREATE_FILE: printf("ERROR. Cannot create output CBI stream."); break; 
				case CBI_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output CBI stream."); break; 
			}
			delete FileNames;
			delete FileNamesCopy;
			return;
		}
		ET += Params.ELAPSED_TIME;
	}
	R = cbi_close_stream(Stream, &Params);
	if (R != 0) {
		switch(R) {
			case BMP_ERROR_OPEN_FILE: printf("ERROR. Cannot open input BMP file."); break; 
			case BMP_ERROR_READ_FILE: printf("ERROR. Cannot read from input BMP file."); break; 
			case BMP_ERROR_FILE_HEADER_CORRUPTED: printf("ERROR. BMP file header is corrupted."); break; 
			case BMP_ERROR_UNSUPORTED_BITS_PER_PIXEL: printf("ERROR. Unsupported BMP file."); break; 
			case BMP_ERROR_UNSUPORTED_COMPRESSION: printf("ERROR. Unsupported BMP file."); break; 
			case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
			case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
			case CBI_ERROR_CREATE_FILE: printf("ERROR. Cannot create output CBI stream."); break; 
			case CBI_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output CBI stream."); break; 
		}
		delete FileNames;
		delete FileNamesCopy;
		return;
	}
	ET += Params.ELAPSED_TIME;
	printf("OK. Elapsed time: %.4lf msec.", ET);

	delete FileNames;
	delete FileNamesCopy;

	printf("\n");
//	_CrtDumpMemoryLeaks();
}
// ....................................................................................................................

// ....................................................................................................................
// Spustenie funkcie 'decode'
// ....................................................................................................................
void __fastcall RunDecode(void) {
	printf("\n..........................................................................");
	printf("\ncbi console version %s", VERSION);
	printf("\n..........................................................................");
	printf("\nDecode:");
	printf("\n..........................................................................");
	printf("\nInput file: %s", InParam_InFileName);
	printf("\nOutput file: %s", InParam_OutFileName);
	printf("\n..........................................................................");

	CONVERT_PARAMS Params; Params.STRUCTURE_SIZE = sizeof(CONVERT_PARAMS);
	Params.PAGE_INDEX = 1;

	double ET = 0;
	printf("\nDecoding... ");

	void *Stream = cbi_open_stream(InParam_InFileName, &Params);
	if (Stream == NULL) {
		switch(Params.ERROR_CODE) {
			case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
			case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
			case CBI_ERROR_OPEN_FILE: printf("ERROR. Cannot open input CBI stream."); break;
			case CBI_ERROR_READ_FILE: printf("ERROR. Cannot read input CBI stream."); break;
			case CBI_ERROR_INVALID_STRUCTURE: printf("ERROR. The input CBI stream is corrupted."); break;
			case CBI_ERROR_PAGE_OUT_OF_RANGE: printf("ERROR. The requested page is out of range."); break;
		}
		return;
	}

	ET += Params.ELAPSED_TIME;

	char FNBase[256];
	char *Ext = strrchr(InParam_OutFileName, '.');
	if (Ext != NULL) {
		strncpy(FNBase, InParam_OutFileName, Ext - InParam_OutFileName);
		FNBase[Ext - InParam_OutFileName] = 0;
	}
	else strcpy(FNBase, InParam_OutFileName);
	if (Ext == NULL) Ext = "";
	char FN[256];

	if (InParam_PagesCount == 0) {
		Params.PAGE_INDEX = 0;
		while(true) {
			sprintf(FN, "%s.%ld%s", FNBase, Params.PAGE_INDEX + 1, Ext);
			int R = cbi_extract_bmp_page(Stream, FN, Params.PAGE_INDEX++, &Params);
			if (R == CBI_ERROR_PAGE_OUT_OF_RANGE) break;
			if (R != 0) {
				switch(R) {
					case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
					case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
					case CBI_ERROR_OPEN_FILE: printf("ERROR. Cannot open input CBI stream."); break;
					case CBI_ERROR_READ_FILE: printf("ERROR. Cannot read input CBI stream."); break;
					case CBI_ERROR_INVALID_STRUCTURE: printf("ERROR. The input CBI stream is corrupted."); break;
				}
				cbi_close_stream(Stream, &Params);
				return;
			}
			ET += Params.ELAPSED_TIME;
		}
	} else {
		for (int i = 0; i < InParam_PagesCount; i++) {
			Params.PAGE_INDEX = InParam_Pages[i];
			sprintf(FN, "%s.%ld%s", FNBase, Params.PAGE_INDEX + 1, Ext);
			int R = cbi_extract_bmp_page(Stream, FN, Params.PAGE_INDEX, &Params);
			if (R == CBI_ERROR_PAGE_OUT_OF_RANGE) break;
			if (R != 0) {
				switch(R) {
					case BMP_ERROR_CREATE_FILE: printf("ERROR. Cannot create output BMP file."); break; 
					case BMP_ERROR_WRITE_FILE: printf("ERROR. Cannot write to output BMP file."); break; 
					case CBI_ERROR_OPEN_FILE: printf("ERROR. Cannot open input CBI stream."); break;
					case CBI_ERROR_READ_FILE: printf("ERROR. Cannot read input CBI stream."); break;
					case CBI_ERROR_INVALID_STRUCTURE: printf("ERROR. The input CBI stream is corrupted."); break;
					case CBI_ERROR_PAGE_OUT_OF_RANGE: printf("ERROR. The requested page is out of range."); break;
				}
				cbi_close_stream(Stream, &Params);
				return;
			}
			ET += Params.ELAPSED_TIME;
		}
	}

	cbi_close_stream(Stream, &Params);

	ET += Params.ELAPSED_TIME;
	printf("OK. Elapsed time: %.4lf msec.", ET);

	printf("\n");
	//_CrtDumpMemoryLeaks();
}
// ....................................................................................................................

// ....................................................................................................................
// Spustenie funkcie 'stream_info'
// ....................................................................................................................
void __fastcall RunStreamInfo(void) {
	printf("\n..........................................................................");
	printf("\ncbi console version %s", VERSION);
	printf("\n..........................................................................");
	printf("\nGet stream info:");
	printf("\n..........................................................................");
	printf("\nInput file: %s", InParam_InFileName);
	printf("\n..........................................................................");
	printf("\nGetting info... ");

	CBI_STREAM_INFO StreamInfo;
	int R = cbi_get_stream_info(InParam_InFileName, &StreamInfo);
	switch(R) {
		case CBI_ERROR_OPEN_FILE: printf("ERROR. Cannot open input CBI stream."); return;
		case CBI_ERROR_READ_FILE: printf("ERROR. Cannot read input CBI stream."); return;
		case CBI_ERROR_INVALID_STRUCTURE: printf("ERROR. The input CBI stream is corrupted."); return;
	}

	PCBI_PAGE_INFO Pages = new CBI_PAGE_INFO[StreamInfo.PAGES_COUNT]; memset(Pages, 0, StreamInfo.PAGES_COUNT*sizeof(CBI_PAGE_INFO));
	R = cbi_get_pages_info(InParam_InFileName, Pages); // Ziskam info o strankach
	if (R != 0) delete Pages; // Chyba?
	switch(R) {
		case CBI_ERROR_OPEN_FILE: printf("ERROR. Cannot open input CBI stream."); return;
		case CBI_ERROR_READ_FILE: printf("ERROR. Cannot read input CBI stream."); return;
		case CBI_ERROR_INVALID_STRUCTURE: printf("ERROR. The input CBI stream is corrupted."); return;
	}

	printf("OK.");

	printf("\n");
	printf("\nStream information:");
	printf("\n\tPages count: %lu", StreamInfo.PAGES_COUNT);
	printf("\n\tCompression: %lu", StreamInfo.COMPRESSION);
	printf("\n\tMax. width count: %lu", StreamInfo.MAX_WIDTH);
	printf("\n\tMax. height count: %lu", StreamInfo.MAX_HEIGHT);
	printf("\n\tUncompressed size: %llu bytes", StreamInfo.ORIGINAL_SIZE);
	printf("\n\tCompressed size: %llu bytes", StreamInfo.COMPRESSED_SIZE);
	printf("\n");

	char BUF[12];

	for (int i = 0; i < (int)StreamInfo.PAGES_COUNT; i++) {
		printf("\nPage %ld information:", i + 1);
		printf("\n\tWidth: %lu", Pages[i].WIDTH);
		printf("\n\tHeight: %lu", Pages[i].HEIGHT);
		sprintf(BUF, "%8X", Pages[i].COLOR_BACK); for (int j = 0; j < (int)strlen(BUF); j++) if (BUF[j] == ' ') BUF[j] = '0';
		printf("\n\tBackground color: #%s", BUF);
		sprintf(BUF, "%8X", Pages[i].COLOR_FORE); for (int j = 0; j < (int)strlen(BUF); j++) if (BUF[j] == ' ') BUF[j] = '0';
		printf("\n\tForeground color: #%s", BUF);
		printf("\n\tUncompressed size: %llu bytes", Pages[i].ORIGINAL_SIZE);
		if (Pages[i].COMPRESSED_SIZE == 0) printf("\n\tCompressed size: N/A");
		else printf("\n\tCompressed size: %llu bytes", Pages[i].COMPRESSED_SIZE);
		printf("\n");
	}
	delete Pages; // Uvolnim pamat
	//_CrtDumpMemoryLeaks();
}
// ....................................................................................................................

// ....................................................................................................................
// Hlavna funkcia aplikacie
// ....................................................................................................................
int main(int pArgc, char** pArgs) {
	if (!DecodeInputParams(pArgc, pArgs)) { // Dekodujem vstupne parametre...
		PrintUsage(); // Nedostatocny pocet
		return 1;
	}

	switch(InParam_Function) {
		case FUNCTION_ENCODE: {
			RunEncode();
							  } break;
		case FUNCTION_DECODE: {
			RunDecode();
							  } break;
		case FUNCTION_STREAM_INFO: {
			RunStreamInfo();
							  } break;
		default: { // Neznama funkcia
			PrintUsage();
				 } break;
	}

	return 0;
}
// ....................................................................................................................
